/*
 * valgrind --track-fds=yes --leak-check=full --undef-value-errors=yes ./server-example
 */
#include <sys/socket.h>
#include <netinet/in.h>

#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

#include <event2/event.h>
#include <event2/util.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>

#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/md5.h>
#include <openssl/rand.h>

#include <sqlite3.h>

#include "cmd.h"
#include "utils.h"

#define COMMUNITY_RSA_PUBLICKEY_NAME "kcommpub.key"
#define DATABASE_NAME "test.db"

#define SERVER_BACKLOG 10

#define KUSER_PUBKEY_ID		0xE0
#define DIGTOKEN_TYPE_ENCRYPT	0xD0
#define DIGTOKEN_TYPE_HASH		0xD1

#define DEFAULT_TIMEOUT		5	/* 5s */
#define MAX_LEN 1024

#define SQLITE3_PATCH

enum client_state {
	STATE_IDLE = 0,
	STATE_DOORID,
	STATE_DIGKEY_TOKEN,
	STATE_PHONE_INFO,
	STATE_RANDOM_NUM,
	//STATE_RANDOM_NUM_ENCRYPT,
	STATE_OPEN,

	MAX_STATE = STATE_OPEN,
};

static const char *__state_str[] = {
	"STATE_IDLE",
	"STATE_DOORID",
	"STATE_DIGKEY_TOKEN",
	"STATE_PHONE_INFO",
	"STATE_RANDOM_NUM",
	//"STATE_RANDOM_NUM_ENCRYPT",
	"STATE_OPEN",
};

struct client {
	enum client_state state;

	struct auth_token *plain_token;
	int token_len;

#if 0
	uint8_t calc_md5[MD5_DIGEST_LENGTH];
	uint8_t md5[MD5_DIGEST_LENGTH];
	uint8_t *info;
	int info_len;
#endif
	RSA *kusrpubkey;
	struct {
		uint8_t imei[IMEI_LEN];
		uint8_t btaddr[BT_ADDR_LEN];
		uint8_t wifiaddr[WIFI_ADDR_LEN];
		uint32_t user_id;
	} phone_info;

	uint8_t random[RANDOM_NUMBER_SIZE];

	struct bufferevent *bev;

	struct event *timeout_event;

	uint8_t resp[MAX_LEN];
	uint32_t flags;
};

#define CHECK_STATE(_state)	\
	do { \
		if (_state != cl->state) {\
			err("err state %s, expect %s", state_str(cl->state), state_str(_state));	\
			return 1; \
		} \
	} while(0)

struct event_base *g_base;

struct client *next_client = NULL;
#ifndef SQLITE3_PATCH
static sqlite3 *db;
#endif

#define KEY_SERVER_PORT 10000
static int server_port = KEY_SERVER_PORT;

static const char *database = DATABASE_NAME;
static RSA *kcomm_pubkey;
static char kcomm_pubkey_path[1024];

static const char *state_str(enum client_state state)
{
	if (state > MAX_STATE)
		return "UNKNOWN";
	return __state_str[state];
}

static int is_user_in_blacklist(uint32_t user_id)
{
	sqlite3_stmt *stmt;
	int ret;
	uint32_t val;
	const char *tail;
	int block = 0;
#ifdef SQLITE3_PATCH
	sqlite3 *db;
#endif

	char *sql = "select user_id from blacklist;";

#ifdef SQLITE3_PATCH
	/* init sqlite3 */
	ret = sqlite3_open_v2(database, &db, SQLITE_OPEN_READWRITE, NULL);
	if (ret) {
		err("can't open database: %s", sqlite3_errmsg(db));
		sqlite3_close(db);
		return 1;
	}
#endif

	ret = sqlite3_prepare_v2(db, sql, -1, &stmt, &tail);
	if (ret != SQLITE_OK) {
		err("SQL error: %s", sqlite3_errmsg(db));
	}
	ret = sqlite3_step(stmt);

	while (ret == SQLITE_ROW) {
		val = sqlite3_column_int(stmt, 0);
		dbg("val from sqlite3: %d/%d", val, user_id);

		if (val == user_id) {
			block = 1;
			break;
		}
		ret = sqlite3_step(stmt);
	}

	sqlite3_finalize(stmt);
#ifdef SQLITE3_PATCH
	sqlite3_close(db);
#endif

	return block;
}

static int handle_request_doorid(struct client *cl, uint8_t *data, uint32_t reqlen)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)cl->resp;
	struct resp_door_id *resp = (struct resp_door_id *)hdr->data;

	CHECK_STATE(STATE_IDLE);

	hdr->bType = CMD_RSP_DOORID;
	hdr->wLength = sizeof(*resp);

	// TODO:
	resp->bSubDoorNum = 1;
	resp->dwDoorID = DOORID;
	resp->dwCommID = COMMID;

	evbuffer_add(bufferevent_get_output(cl->bev), hdr, CMD_HDR_LEN + sizeof(*resp));

	cl->state = STATE_DOORID;

	return 0;
}

/* TLV format: */
static int parse_decrypted_token(struct client *cl, struct auth_token *token, int len)
{
	BIO *key;
	int ret = 0;
	int kuserpubkey_len = len - sizeof(struct auth_token);

	dump(__func__, token->strUserPub, kuserpubkey_len);
	key = BIO_new(BIO_s_mem());
	BIO_write(key, token->strUserPub, kuserpubkey_len);
	cl->kusrpubkey = PEM_read_bio_RSAPublicKey(key, NULL, NULL, NULL);
	if (!cl->kusrpubkey) {
		err("cannot read kuserpub key");
		ret = 1;
	}
	BIO_free(key);

#if 0
	if (len < CMD_HDR_LEN)
		return 1;

	ret = 0;
	while (p < q) {
		type = p[0];
		sublen = p[1] | (p[2] << 8);
		dbg("%s %d, type %c, len %d", __func__, __LINE__, type, sublen);

		switch (type) {
		case TOKEN_TYPE_PUBKEY:
			key = BIO_new(BIO_s_mem());
			BIO_write(key, &p[3], sublen);
			dump(__func__, &p[3], sublen);
			cl->kusrpubkey = PEM_read_bio_RSAPublicKey(key, NULL, NULL, NULL);
			BIO_free(key);
			if (!cl->kusrpubkey) {
				err("cannot read kuserpub key");
				ret = 1;
				break;
			} else {
				dbg("read kuserpubkey successfully");
			}
			break;

		default:
			dbg("other info %d", type);
			break;
		}
		p += sublen + CMD_HDR_LEN;
		
	}

#endif
	return ret;
}

// data: encrypted token
static int decrypt_token(struct client *cl, BIO *enc_token)
{
	int enc_len = BIO_number_written(enc_token);	//total length of encrpted token
	int flen, remain, icount, ret;
	uint8_t *cipher_block, *plain_block;
	BIO *plain_token;

	flen = RSA_size(kcomm_pubkey);
	cipher_block = malloc(flen);
	plain_block = malloc(flen);
	remain = enc_len;
	icount = flen;

	dbg("encrypted token len: %d, flen: %d", enc_len, flen);

	plain_token = BIO_new(BIO_s_mem());

	for (remain = enc_len; remain; remain -= icount) {
		icount = BIO_read(enc_token, cipher_block, flen);
		//dump(__func__, cipher_block, icount);
		if (icount != flen) {
			err("warning: flen != icount");
		}
		dbg("cipher block");
		dump(__func__, cipher_block, icount);
		ret = RSA_public_decrypt(flen, cipher_block, plain_block, kcomm_pubkey, RSA_PKCS1_PADDING);
		if (ret <= 0) {
			err("%s %d: decrypted failed", __func__, __LINE__);
			goto failed;
		}
		dbg("plain block");
		dump(__func__, plain_block, ret);
		BIO_write(plain_token, plain_block, ret);
	}

	cl->token_len = BIO_number_written(plain_token);
	cl->plain_token = malloc(cl->token_len);
	BIO_read(plain_token, cl->plain_token, cl->token_len);
	dbg("decrypted token len: %d", cl->token_len);

	ret = parse_decrypted_token(cl, cl->plain_token, cl->token_len);

	goto out;

failed:
	ret = 1;

out:
	//TODO: free buf
	BIO_free(plain_token);
	free(cipher_block);
	free(plain_block);

	return ret;
}

#if 0
static int calc_plain_token_md5(struct client *cl)
{
	// assert plain token exist
	if (cl->plain_token) {
		MD5(cl->plain_token, cl->token_len, cl->calc_md5);
		// dump md5 info
	}
	return 0;
}
#endif


static int handle_request_phone_info(struct client *cl, void *data, int reqlen)
{
	struct req_phone_info *req_info = data;
	struct cmd_hdr *hdr = (struct cmd_hdr *)cl->resp;
	struct resp_phone_info *resp = (struct resp_phone_info *)hdr->data;

	resp->bInfoCheckError = 0;
	hdr->bType = CMD_RSP_PHONE_INFO;
	hdr->wLength = sizeof(*resp);

	/* do compare */
	if (memcmp(req_info->strBTAddress, cl->plain_token->strBTAddress, BT_ADDR_LEN))
		resp->bInfoCheckError |= INFO_ERROR_BT_ADDR;

	if (memcmp(req_info->strMACAddress, cl->plain_token->strMACAddress, WIFI_ADDR_LEN))
		resp->bInfoCheckError |= INFO_ERROR_WIFI_ADDR;

	if (memcmp(req_info->strIMEI, cl->plain_token->strIMEI, IMEI_LEN))
		resp->bInfoCheckError |= INFO_ERROR_IMEI;

	evbuffer_add(bufferevent_get_output(cl->bev), hdr, CMD_HDR_LEN + sizeof(*resp));

	return resp->bInfoCheckError ? 1 : 0;
}

static int handle_request_digkey_token(struct client *cl, uint8_t *data, int reqlen)
{
	BIO *enc_token;
	struct cmd_hdr *hdr;
	struct resp_dig_token *resp;

	CHECK_STATE(STATE_DOORID);

	enc_token= BIO_new(BIO_s_mem());
	BIO_write(enc_token, data, reqlen);

	if (decrypt_token(cl, enc_token)) {
		err("decrypt failed");
		BIO_free(enc_token);
		return 1;
	}
	BIO_free(enc_token);

#if 0
	uint8_t type;
	uint8_t *end;
	uint8_t *p;
	int sublen;

	end = data + len;
	p = data;

	dbg("%s %d: data %p, end %p, len %d", __func__, __LINE__, data, end, len);

	if (len < CMD_HDR_LEN)
		return 1;

	while (p < end) {
		type = p[0];
		sublen = p[1] | (p[2] << 8);

		dbg("%s %d: type %d, sublen %d", __func__, __LINE__, type, sublen);
		switch (type) {
		case DIGTOKEN_TYPE_ENCRYPT:
			// digkey
			dbg("%s %d", __func__, __LINE__);
			//flen = RSA_size(kcomm_pubkey);
			enc_token= BIO_new(BIO_s_mem());
			BIO_write(enc_token, &p[3], sublen);
			if (decrypt_token(cl, enc_token)) {
				err("decrypt failed");
				BIO_free(enc_token);
				return 1;
			}

			// calculate md5 of token.
			calc_plain_token_md5(cl);
			BIO_free(enc_token);
			break;

		case DIGTOKEN_TYPE_HASH:
			// other infomation, md5 hash
			//assert(sublen);
			dbg("%s %d", __func__, __LINE__);
			if (sublen > MD5_DIGEST_LENGTH) {
				err("len exec");
				return 1;
			}
			memcpy(cl->md5, &p[3], MD5_DIGEST_LENGTH);
			break;
		default:
			break;
		}

		p += CMD_HDR_LEN + sublen;
		//dbg("%s %d: p %p, end %p", __func__, __LINE__, p, end);
	}
	
	if (!cl->plain_token || !cl->kusrpubkey) {
		err("doesn't have full information");
		return 1;
	}

	if (memcmp(cl->calc_md5, cl->md5, MD5_DIGEST_LENGTH)) {
		err("md5 not the same, ignore...");
		//return 1;
	}
#endif

	if (is_user_in_blacklist(123456789)) {
		dbg("user in block, ignore in test mode...");
	}

	hdr = (struct cmd_hdr *)cl->resp;
	resp = (struct resp_dig_token *)hdr->data;

	hdr->bType = CMD_RSP_DIGKEY_TOKEN;
	hdr->wLength = sizeof(struct resp_dig_token);
	resp->bKeyCheckError = 0;	// success.

	evbuffer_add(bufferevent_get_output(cl->bev), hdr, CMD_HDR_LEN + sizeof(*resp));

	cl->state = STATE_DIGKEY_TOKEN;

	return 0;
}

static int handle_request_random_number(struct client *cl, uint8_t *data, int len)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)cl->resp;

	CHECK_STATE(STATE_DIGKEY_TOKEN);

	RAND_pseudo_bytes(cl->random, sizeof(cl->random));

	dump(__func__, cl->random, sizeof(cl->random));

	hdr->bType = CMD_RSP_RANDOM_NUM;
	hdr->wLength = RANDOM_NUMBER_SIZE;
	memcpy(hdr->data, cl->random, RANDOM_NUMBER_SIZE);

	evbuffer_add(bufferevent_get_output(cl->bev), hdr, CMD_HDR_LEN + RANDOM_NUMBER_SIZE);

	cl->state = STATE_RANDOM_NUM;

	return 0;
}

static int log_unlock_door_entry(const char *time, const char *type, const char *imei)
{
	sqlite3_stmt *stmt;
	const char *sql = "insert into log (time, type, imei) values (?,?,?);";
	const char *tail;
	int ret;
#ifdef SQLITE3_PATCH
	sqlite3 *db;

	/* init sqlite3 */
	ret = sqlite3_open_v2(database, &db, SQLITE_OPEN_READWRITE, NULL);
	if (ret) {
		err("can't open database: %s", sqlite3_errmsg(db));
		sqlite3_close(db);
		return 1;
	}
#endif

	ret = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, &tail);
	if (ret != SQLITE_OK) {
		err("SQL error: %s", sqlite3_errmsg(db));
		return 1;
	}

	sqlite3_bind_text(stmt, 1, time, strlen(time), NULL);
	sqlite3_bind_text(stmt, 2, type, type ? strlen(type) : 0, NULL);
	sqlite3_bind_text(stmt, 3, imei, imei ? strlen(imei): 0, NULL);

	sqlite3_step(stmt);
	sqlite3_finalize(stmt);
#ifdef SQLITE3_PATCH
	sqlite3_close(db);
#endif

	return 0;
}

static int check_sys_door_files(void)
{
	struct stat statbuf;
	int fd;
	char buf[16];

	if (stat("/sys/class/gpio/gpio17", &statbuf) < 0) {

		fd = open("/sys/class/gpio/export", O_WRONLY);
		if (fd < 0) {
			err("%s %d: Couldn't populate gpio files", __func__, __LINE__);
			return 1;
		}

		strcpy(buf, "17");
		if (write(fd, buf, 3) < 0) {
			err("%s %d: Couldn't populate gpio files", __func__, __LINE__);
			return 1;
		}
		close(fd);


		/* set gpio to output mode */
		fd = open("/sys/class/gpio/gpio17/direction", O_WRONLY);
		if (fd < 0) 
			return 1;
		strcpy(buf, "out");
		if (write(fd, buf, 4) < 0) {
			err("%s %d: Couldn't populate gpio files", __func__, __LINE__);
			return 1;
		}
		close(fd);
	}

	return 0;
}

static int open_close_door(int what)
{
	char buf[16];
	int fd;

	if (check_sys_door_files())
		return 1;

	fd = open("/sys/class/gpio/gpio17/value", O_WRONLY);
	if (fd < 0) 
		return 1;

	if (what)
		strcpy(buf, "1");
	else
		strcpy(buf, "0");

	if (write(fd, buf, 2) < 0) {
		err("Couldn't write value to sysfs");
		close(fd);

		return 1;
	}

	close(fd);
	return 0;
}

static int open_door(void)
{
	return open_close_door(1);
}

static int close_door(void)
{
	return open_close_door(0);
}

static int handle_request_encrypt_random_number(struct client *cl, uint8_t *data, int len)
{
	int flen;
	uint8_t *temp;
	int ret;
	struct cmd_hdr *hdr = (struct cmd_hdr *)cl->resp;
	struct resp_random_encrypt *resp = (struct resp_random_encrypt *)hdr->data;

	CHECK_STATE(STATE_RANDOM_NUM);

	resp->bRandom = RANDOM_SUCCESS;

	flen = RSA_size(cl->kusrpubkey);

	dbg("random number enc len: %d", len);
	if (len > flen /* - 11*/) {
		err("%s len exec\n", __func__);
		return 1;
	}

	if (!(temp = malloc(flen))) {
		err("%s malloc failed", __func__);
		return 1;
	}

	// decrypt
	ret = RSA_public_decrypt(len, data, temp, cl->kusrpubkey, RSA_PKCS1_PADDING);
	if (ret <= 0) {
		err("decrypt random number failed");
		return 1;
	}

	dbg("%s: decrypted len %d", __func__, ret);
	dump("random number", temp, ret);
	dump("random number", cl->random, RANDOM_NUMBER_SIZE);

	if (memcmp(temp, cl->random, RANDOM_NUMBER_SIZE)) {
		err("%s random not the same", __func__);
		free(temp);
		return 1;
	}

	free(temp);

	hdr->bType = CMD_RSP_ENCRYPT_RANDOM_NUM;
	hdr->wLength = sizeof(*resp);
	evbuffer_add(bufferevent_get_output(cl->bev), hdr, 
			CMD_HDR_LEN + sizeof(*resp));

	cl->state = STATE_OPEN;

	evtimer_del(cl->timeout_event);

	return resp->bRandom == RANDOM_SUCCESS ? 0 : 1;

#if 0
	time_t time_val;

	open_door();

	time(&time_val);

	log_unlock_door_entry(ctime(&time_val), NULL, NULL);

	resp = cl->resp;
	resp[0] = CMD_REQUEST_ENCRYPT_RANDOM_NUM;
	resp[1] = resp[2] = 0;

	ustream_write(&cl->s.stream, (char *)resp, 3, false);

	cl->state = STATE_OPEN;

	return 0;
#endif
}

void close_door_cb(evutil_socket_t fd, short what, void *ctx)
{
	close_door();
}

static int handle_request_open_door(struct client *cl, void *data, int len)
{
	struct req_open_door *req = data;
	struct cmd_hdr *hdr = (struct cmd_hdr *)cl->resp;
	struct resp_open_door *resp = (struct resp_open_door *)hdr->data;
	time_t time_val;
	struct timeval tv = {5, 0};
	struct event *ev;

	int sub_doorid = req->bSubDoorID;
	uint16_t rssi = req->wRSSI;


	CHECK_STATE(STATE_OPEN);
	err("rssi %d, sub_doorid %d", rssi, sub_doorid);

	hdr->bType = CMD_RSP_OPENDOOR;
	hdr->wLength = sizeof(*resp);
	resp->bOpenResult = OPENDOOR;

	evbuffer_add(bufferevent_get_output(cl->bev), hdr, 
			CMD_HDR_LEN + sizeof(*resp));


	open_door();

	time(&time_val);

	log_unlock_door_entry(ctime(&time_val), NULL, NULL);


	/* close door after 5s */
#if 1//DEBUG
	ev = evtimer_new(g_base, close_door_cb, cl);
	evtimer_add(ev, &tv);
#endif

	return 0;
}


static void handle_client_request(struct client *cl, void * data)
{
	struct cmd_hdr *hdr = data;
	enum cmd_id cmd = hdr->bType;

	int ret = 0;

	switch (cmd) {
	case CMD_REQ_DOORID:
		err("received request doorid");
		ret = handle_request_doorid(cl, hdr->data, hdr->wLength);
		break;

	case CMD_REQ_DIGKEY_TOKEN:
		err("received request digkey");
		ret = handle_request_digkey_token(cl, hdr->data, hdr->wLength);
		break;

	case CMD_REQ_PHONE_INFO:
		err("received phoneinfo");
		ret = handle_request_phone_info(cl, hdr->data, hdr->wLength);
		break;

	case CMD_REQ_RANDOM_NUM:
		err("received random number");
		ret = handle_request_random_number(cl, hdr->data, hdr->wLength);
		break;

	case CMD_REQ_ENCRYPT_RANDOM_NUM:
		err("received encrypt random number");
		ret = handle_request_encrypt_random_number(cl, hdr->data, hdr->wLength);
		break;

	case CMD_REQ_OPENDOOR:
		err("received open door request");
		ret = handle_request_open_door(cl, hdr->data, hdr->wLength);
		break;

	default:
		// pass
		break;
	}

	if (ret) {
		err("failed, abort connection");
	}
}

static void client_close(struct client *cl)
{
	evtimer_del(cl->timeout_event);
	event_free(cl->timeout_event);
	bufferevent_free(cl->bev);

	if (cl->plain_token)
		free(cl->plain_token);
	if (cl->kusrpubkey)
		RSA_free(cl->kusrpubkey);

	free(cl);
}

static void client_read_cb(struct bufferevent *bev, void *ctx)
{
	struct client *cl = ctx;
	size_t datalen;
	struct cmd_hdr *hdr;

	/* This callback is invoked when there is data to read on bev. */
	struct evbuffer *input = bufferevent_get_input(bev);
	//struct evbuffer *output = bufferevent_get_output(bev);
	//evbuffer_get_length(st struct evbuffer * buf)

	/* make sure hdr + data are in linear memory space. */
	hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN);
	if (!hdr) {
		err("not enough data in the buffer");
		return;
	}

	datalen = hdr->wLength;
	if (datalen) {
		hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN + datalen);
		if (!hdr){
			err("not enough data in the buffer");
			return;
		}
	}

	dump(__func__, hdr, CMD_HDR_LEN + datalen);
	handle_client_request(cl, hdr);

	evbuffer_drain(input, CMD_HDR_LEN + datalen);
}

static void client_event_cb(struct bufferevent *bev, short events, void *ctx)
{
	struct client *cl = ctx;

	if (events & BEV_EVENT_ERROR)
		err("Error from bufferevent");

	if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
		err("connection closed, events %x, %x %x", events, BEV_EVENT_EOF, BEV_EVENT_ERROR);

		//request_done(req);
		client_close(cl);
	}
}

void client_timeout_cb(evutil_socket_t fd, short what, void *arg)
{
	struct client *cl = arg;

	err("!!! %s !!!", __func__);
	// close client
	client_close(cl);
}

void accept_conn_cb(struct evconnlistener *listener,
		evutil_socket_t fd, struct sockaddr *addr, int socklen, void *ctx)
{
	/* We got a new connection! Set up a bufferevent for it. */
	struct event_base *base = evconnlistener_get_base(listener);
	struct bufferevent *bev = bufferevent_socket_new(
			base, fd, BEV_OPT_CLOSE_ON_FREE);
	struct client *cl = calloc(1, sizeof(*cl));
	struct timeval tv = {DEFAULT_TIMEOUT, 0};

	cl->bev = bev;
	cl->timeout_event = evtimer_new(base, client_timeout_cb, cl);
	evtimer_add(cl->timeout_event, &tv);

	bufferevent_setcb(bev, client_read_cb, NULL, client_event_cb, cl);

	bufferevent_enable(bev, EV_READ|EV_WRITE);
}

void accept_error_cb(struct evconnlistener *listener, void *ctx)
{
	struct event_base *base = evconnlistener_get_base(listener);
	int err = EVUTIL_SOCKET_ERROR();
	err("Got an error %d (%s) on the listener. Shutting down.",
			err, evutil_socket_error_to_string(err));

	event_base_loopexit(base, NULL);
}

static int run_server(void)
{
	FILE *public_file;

	struct evconnlistener *listener;
	struct sockaddr_in sin;

	if (!(g_base = event_base_new())) {
		err("Couldn't open event base");
		return 1;
	}

#ifndef SQLITE3_PATCH
	/* init sqlite3 */
	ret = sqlite3_open_v2(database, &db, SQLITE_OPEN_READWRITE, NULL);
	if (ret) {
		err("can't open database: %s", sqlite3_errmsg(db));
		sqlite3_close(db);
		return 1;
	}
#endif

	/* init openssl */
	CRYPTO_malloc_init(); 
	OpenSSL_add_all_algorithms(); 

	if (!(public_file = fopen(kcomm_pubkey_path, "rb"))) {
		err("cannot load public key file");
		return 1;
	}

	kcomm_pubkey = PEM_read_RSAPublicKey(public_file, NULL, NULL, NULL);
	if (!kcomm_pubkey) {
		err("cannot load public key");
		return 1;
	}


	/* init network */
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(0);
	sin.sin_port = htons(server_port);

	listener = evconnlistener_new_bind(g_base, accept_conn_cb, NULL,
			LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, SERVER_BACKLOG,
			(struct sockaddr *)&sin, sizeof(sin));

	if (!listener) {
		err("Couldn't create listener");
		return 1;
	}

	evconnlistener_set_error_cb(listener, accept_error_cb);

	err("server-example starting on port %d", server_port);

	event_base_dispatch(g_base);

	evconnlistener_free(listener);
#ifndef SQLITE3_PATCH
	/* cleanup */
	sqlite3_close(db);
#endif

	return 0;
}

static int usage(const char *name)
{
	err("Usage: %s [-p <port>] [-k <path/to/kserver_pub_key>]", name);
	return 0;
}

int main(int argc, char **argv)
{
	int ch;

	strcpy(kcomm_pubkey_path, COMMUNITY_RSA_PUBLICKEY_NAME);

	while ((ch = getopt(argc, argv, "d:p:k:h")) != -1) {
		switch(ch) {
		case 'p':
			server_port = atoi(optarg);
			break;
		case 'd':
			database = optarg;
			break;
		case 'k':
			strcpy(kcomm_pubkey_path, optarg);
			break;
		case 'h':
			/* fall through */
		default:
			return usage(argv[0]);
		}
	}

	return run_server();
}
